package com.cps.frameauth.common.aspect;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.cps.frameauth.common.annotation.ThesisToken;
import com.cps.frameauth.common.constants.JwtConstant;
import com.cps.frameauth.common.header.Header;
import com.cps.frameauth.model.dto.JwtBody;
import com.cps.frameauth.model.dto.UserLoginDto;
import com.cps.frameauth.model.enums.TokenCodeEnum;
import com.cps.frameauth.service.TokenService;
import com.cps.framecommon.constant.enums.HeaderOsEnum;
import com.cps.framecommon.exception.BusinessException;
import com.cps.frameauth.model.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * 社区 token切面
 * @author caopeisheng
 */
@Aspect
@Component
@Slf4j
public class HobbyTokenAspect {

    @Resource
    private TokenService tokenService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private ThreadPoolTaskExecutor commonThreadExecutor;

    public static final String LOGIN_COUNT_LOCK_PREFIX = "USER:LOGIN:RECORD:LOCK:";
    public static final String LOGIN_COUNT_PREFIX = "USER:LOGIN:COUNT:LIST";
    public static final int MINUTES_OF_DAY = 24 * 60;


    /**
     * 用户Token key
     */
    public static final String USER_TOKEN = "USER:TOKEN:";

    @Pointcut("@annotation(com.cps.frameauth.common.annotation.ThesisToken)")
    public void point() {
    }

    /**
     * 切面验证主入口
     *
     * @throws Throwable 异常抛出
     */
    @Before(value = "point() && @annotation(thesisToken)")
    public void verifyTokenForClass(ThesisToken thesisToken) throws Throwable {
        if (thesisToken.required()) {
            checkToken();
        } else {
            checkTokenWithOutRequired();
        }
    }


    /**
     * 校验token
     *
     * @throws NoSuchAlgorithmException s
     */
    private void checkToken() throws NoSuchAlgorithmException {
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(requestAttributes)) {
            return;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        String token = request.getHeader(JwtConstant.TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
            log.info("checkToken error requestUrl:{} deviceId:{}, oaId:{} no token", request.getRequestURL(), request.getHeader(Header.OA_ID.key), request.getHeader(Header.DEVICE_ID.key));
            throw new BusinessException(
                    TokenCodeEnum.AUTH_TOKEN_EMPTY.getCode(), TokenCodeEnum.AUTH_TOKEN_EMPTY.getMessage());
        }
        log.info("start check token:{} url:{}", token, request.getRequestURL());
        JwtBody body = tokenService.parseToken(token);
        checkTokenExit(body, token, request.getHeader(Header.DEVICE_ID.key), request.getHeader(Header.OA_ID.key));
        request.setAttribute("user", body);
        String device = request.getHeader("OS");
        if (HeaderOsEnum.ANDROID.code().equals(device) || HeaderOsEnum.IOS.code().equals(device)) {
            log.info("start record login log requestUrl:{}", request.getRequestURL());
            recordLoginLog(body, request.getHeader(Header.OA_ID.key), request.getHeader(Header.DEVICE_ID.key));
        }

    }

    /**
     * 解析token 完成记录当日登录次数
     *
     * @param body body
     */
    private void recordLoginLog(JwtBody body, String oaId, String deviceId) {
        commonThreadExecutor.execute(() -> {
            //解析成功 放到redis用户记录用户登录
            User user = JSON.parseObject(JSON.toJSON(body.getObject()).toString(), User.class);
            String lockKey = LOGIN_COUNT_LOCK_PREFIX + DateUtil.today() + ":" + user.getUserId();
            Boolean todayFlag = Boolean.TRUE;

            log.info("recordLoginLog user:{}, osId:{}, deviceId:{}", JSON.toJSONString(user), oaId, deviceId);
            if (StringUtils.isNotEmpty(oaId) || StringUtils.isNotEmpty(deviceId)) {
                todayFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", MINUTES_OF_DAY, TimeUnit.MINUTES);
            } else {
                Object obj = redisTemplate.opsForValue().get(lockKey);
                if (Objects.nonNull(obj)) {
                    todayFlag = Boolean.FALSE;
                }
            }
            if (Boolean.TRUE.equals(todayFlag)) {
                UserLoginDto loginDto = new UserLoginDto();
                loginDto.setUser(user);
                loginDto.setDate(DateUtil.today());
                loginDto.setDeviceId(deviceId);
                loginDto.setOaId(oaId);
                redisTemplate.opsForList().leftPush(LOGIN_COUNT_PREFIX, loginDto);
            }
        });
    }

    /**
     * 校验token
     *
     * @throws NoSuchAlgorithmException s
     */
    private void checkTokenWithOutRequired() throws NoSuchAlgorithmException {
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(requestAttributes)) {
            return;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        String token = request.getHeader(JwtConstant.TOKEN_NAME);
        if (StringUtils.isNotEmpty(token)) {
            JwtBody body = tokenService.parseToken(token);
            checkTokenExit(body, token, request.getHeader(Header.DEVICE_ID.key), request.getHeader(Header.OA_ID.key));
            request.setAttribute("user", body);
            String device = request.getHeader("OS");
            if (HeaderOsEnum.ANDROID.code().equals(device) || HeaderOsEnum.IOS.code().equals(device)) {
                log.info("no required token start record login log requestUrl:{}", request.getRequestURL());
                recordLoginLog(body, request.getHeader(Header.OA_ID.key), request.getHeader(Header.DEVICE_ID.key));
            }

        }

    }

    /**
     * 判断token是否合法
     *
     * @param body  body
     * @param token token
     */
    private void checkTokenExit(JwtBody body, String token, String deviceId, String oaId) {
        try {
            User user = JSON.parseObject(JSON.toJSON(body.getObject()).toString(), User.class);
            Object value = redisTemplate.opsForValue().get(USER_TOKEN + user.getUserId());
            if (!token.equals(value)) {
                log.error("checkTokenExit fail token:{}, redisToken:{}, deviceId:{},oaId:{}", token, value, deviceId, oaId);
                throw new BusinessException(
                        TokenCodeEnum.AUTH_TOKEN_ILLEGAL.getCode(),
                        TokenCodeEnum.AUTH_TOKEN_ILLEGAL.getMessage());
            }

        } catch (Exception e) {
            log.error("checkTokenExit error:{}, token:{}", e, token);
            throw new BusinessException(
                    TokenCodeEnum.AUTH_TOKEN_ILLEGAL.getCode(),
                    TokenCodeEnum.AUTH_TOKEN_ILLEGAL.getMessage());
        }
    }
}
